#!/usr/bin/env python

#   Trolls Outta Luckland v0.0.6
#   Copyright 2009-2010, Gummbum
#
#   This file is part of Trolls Outta Luckland.
#
#   Trolls Outta Luckland is free software: you can redistribute it
#   and/or modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation, either version 3 of
#   the License, or (at your option) any later version.
#
#   Trolls Outta Luckland is distributed in the hope that it will be
#   useful, but WITHOUT ANY WARRANTY; without even the implied warranty
#   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with Trolls Outta Luckland.  If not, see
#   <http://www.gnu.org/licenses/>.

"""
"""

__version__ = '0.0.4'
__revision__ = '0.0.7'

###Python imports
import os
from os.path import join as join_path
import sys
import random
from random import randrange

try:
    ### Pygame imports
    import pygame
    from pygame.locals import Color, RLEACCEL, K_0
    ### Game library imports
    import game_globals
    from game_globals import *
    import gumm_sound
    from vec2d import *
    from pixelperfect import get_colorkey_hitmask
    from sprite_strip_anim import SpriteStripAnim
    from ui import Herald
    from utils import (
        calc_angle, calc_line, calc_circumference_point, constrain, distance,
        draw_text, get_font, load_image, plot_curve
    )
except ImportError, e:
    print 'ImportError: %s' % e
    print '    in file "%s"' % os.path.abspath(sys.argv[0])

(ENEMY_CONVOY,
ENEMY_GOTO_STANDBY,
ENEMY_STANDBY,
ENEMY_PATROL,
ENEMY_REFORM,
ENEMY_RAM) = range(6)

SCORE = (
    50,     # ENEMY_CONVOY
    1,      # ENEMY_GOTO_STANDBY
    50,     # ENEMY_STANDBY
    250,    # ENEMY_PATROL
    1,      # ENEMY_REFORM
    500,    # ENEMY_RAM
)

# Globals initialized by EnemyFactory constructor
game = None
window = None
game_rect = None
jukebox = None

# Ship angle size of 1 makes turns *very* smooth at the significant cost of
# initial loading time.
_SPACE1_SPRITE_SHEET = join_path('image', 'SPACE1x150.png')
_SPACE2_SPRITE_SHEET = join_path('image', 'SPACE2x150.png')
_FIGHTER_HITMASKS_FILENAME = join_path('lib', 'fighter_hitmasks')
_SWEEPER_HITMASKS_FILENAME = join_path('lib', 'sweeper_hitmasks')
_SHIP_ANGLE_SIZE = 8

class LaserBlast(pygame.sprite.Sprite):
    def __init__(self, ship, color='orange'):
        jukebox['enemy fires laser'].play()
        pygame.sprite.Sprite.__init__(self)
        self.ship = ship
        self.speed = LASER_SHOT_SPEED * game.speed()
        self.image,self.rect = load_image(
            join_path('image','laserblast_%s.png'%color), -1)
        self.image = pygame.transform.rotate(self.image, ship.angle)
        self.angle = -ship.angle + 360
        self.rect = self.image.get_rect()
        self.rect.center = ship.rect.center
        self.fcenter = ship.rect.center
        self.hitmask = get_colorkey_hitmask(self.image, self.rect, -1)
        game_globals.game.groups.add(self, layer='enemy_shots')
    def update(self, *args):
        move_to = calc_circumference_point(
            self.speed, self.fcenter, self.angle, to_screen=True)
        self.fcenter = move_to
        self.rect.center = int(round(move_to[0])), int(round(move_to[1]))
        if not game.window.rect.collidepoint(self.rect.center):
            self.kill()
            self.ship.shots -= 1

class EnemyExplodes(pygame.sprite.Sprite):
    def __init__(self, pos):
        pygame.sprite.Sprite.__init__(self)
        jukebox['enemy explodes'].play()
        self.pos = pos
        self.images = SpriteStripAnim(join_path('image', 'Explode1x200.png'),
            (0,1,48,48), 8, -1, False, game.fps.fps/12, name='enemy_explodes')
        self.images.iter()
    def update(self, *args):
        try:
            self.image = self.images.next()
        except StopIteration:
            self.kill()
        self.rect = self.image.get_rect()
        self.rect.center = self.pos

class Enemy(pygame.sprite.Sprite):
    """base class for enemy ships"""
    def __init__(self, rank, pos):
        pygame.sprite.Sprite.__init__(self)
        game = game_globals.game
        self.rect = pygame.Rect((1,1,1,1))
        self.rect.centerx,self.rect.top = pos
        self.rank = rank
        self.points = None
        self.path = None
        self.step = 0
        self.speed = ENEMY_SPEED * game.speed()
        self.shots = 0
        self.fire_rate = round(ENEMY_FIRE_RATE * game.speed())
        self.spec_rate = round(ENEMY_FIRE_RATE * game.speed() / 2)
        self.state = ENEMY_CONVOY
        self.angle = 0
        self.countdown = game.avg_fps/120
    def update(self, *args):
        pygame.sprite.Sprite.update(self)
        p1 = self.rect.center
        state = self.state
        if state == ENEMY_STANDBY:
#            print 'state: ENEMY_STANDBY ...'
            self.rect.center = _STANDBY_POS[self.rank[0]][self.rank[1]]
            if self.countdown <= 0:
                if self.angle > 0 and self.angle < 180:
                    self.angle -= _SHIP_ANGLE_SIZE
                elif self.angle >= 180 and self.angle < 360:
                    self.angle += _SHIP_ANGLE_SIZE
                self.image,self.hitmask = self.anim.get_angle(
                    random.choice((0,0,0,0,0,1)), self.angle)
                pos = self.rect.center
                self.rect = self.image.get_rect()
                self.rect.center = pos
                self.countdown = game.avg_fps/120
            else:
                self.countdown -= 1
        elif round(self.step) < len(self.path):
            # More on this path remains: keep moving ship
            path = self.path
            step = self.step
            rect = self.rect
            path_length = len(self.path)
            center = rect.center
            speed = self.speed
            if self.state in (ENEMY_CONVOY,ENEMY_GOTO_STANDBY):
                speed *= 1.5
            while distance(center, path[int(round(step))]) < speed:
#                sys.stdout.write('x')
                step += speed
                if round(step) >= path_length:
                    step = path_length - 1
                    break
#            sys.stdout.write('.')
            rect.center = self.path[int(round(step))]
            self.step = step + speed
            if state in (ENEMY_PATROL,ENEMY_CONVOY):
                self.shoot()
        elif state == ENEMY_CONVOY:
#            print 'state: ENEMY_CONVOY -> ENEMY_GOTO_STANDBY'
            ## Compute new control points; this ends up plotting a curve
            ## along an inverted L
            #
            # last path -> same vector -> midway between... -> standby pos
            fm = vec2d(self.points[-2])
            to = vec2d(self.points[-1])
            dx = fm.x - to.x
            dy = fm.y - to.y
            fm = vec2d(self.path[-1])
            ## fm=last path
            points = [fm]
            to = vec2d(fm.x-dx,fm.y-dy)
            ## upwards along same vector
            points.append(to)
            fm = to
            rankx,ranky = self.rank[0],self.rank[1]
            to = vec2d(_STANDBY_POS[rankx][ranky])
            dx = fm.x - to.x
            dy = fm.y - to.y
            midx = fm.x - dx/2
            midy = fm.y - dy/2
            ## left or right, midway between...
            points.append(vec2d(midx,midy))
            ## final destination: standby pos
            points.append(vec2d(to))
            self.new_path(points)
            self.state = ENEMY_GOTO_STANDBY
        elif state == ENEMY_GOTO_STANDBY:
#            print 'state: ENEMY_GOTO_STANDBY -> ENEMY_STANDBY'
            rankx,ranky = self.rank[0],self.rank[1]
            x,y = _STANDBY_POS[rankx][ranky]
            self.path = [vec2d(x, y)]
            self.step = 0
            self.state = ENEMY_STANDBY
#            print 'state: ENEMY_STANDBY...'
        elif state == ENEMY_PATROL:
            rect = self.rect
#            print 'state: ENEMY_PATROL -> ENEMY_REFORM'
            fm = vec2d(rect.centerx,-50)
            to = vec2d(rect.centerx, 0)
            points = [vec2d(fm),vec2d(to)]
            fm = to
            rankx,ranky = self.rank[0],self.rank[1]
            to = _STANDBY_POS[rankx][ranky]
            for i in range(2,4):
                midx = fm.x + int((to.x - fm.x) * i/3)
                midy = fm.y + int((to.y - fm.y) * i/3)
                points.append(vec2d(midx,midy))
            self.new_path(points)
            self.state = ENEMY_REFORM
        elif state == ENEMY_REFORM:
#            print 'state: ENEMY_REFORM -> ENEMY_STANDBY'
            rankx,ranky = self.rank[0],self.rank[1]
            self.rect.center = _STANDBY_POS[rankx][ranky]
#            print 'state: ENEMY_STANDBY...'
            self.state = ENEMY_STANDBY
        if state != ENEMY_STANDBY:
            i = int(round(self.step+10))
            if i < len(self.path):
                p2 = self.path[i]
                self._set_image(p1, p2)
    def hit(self, others):
        for other in others:
            self.kill()
            other.kill()
            game.score.add(self.tweak_score(SCORE[self.state]))
            game.groups.add(EnemyExplodes(
                self.rect.center), layer='enemies', under=True)
    def power_up(self, other):
        """to be determined"""
        pass
    def shoot(self):
        """enemy attack logic; override and call the parent method"""
        if self.fire_rate > 0:
            self.fire_rate -= 1
        if self.spec_rate > 0:
            self.spec_rate -= 1
    def _strafing_attack(self):
        """aimless strafing attack"""
        path_len = len(self.path)
        step = int(round(self.step))
        if path_len < 10:
            x1,y1 = self.path[0]
            x2,y2 = self.path[-1]
        elif path_len > step + 9:
            x1,y1 = self.path[step]
            x2,y2 = self.path[step+9]
        else:
            x1,y1 = self.path[-1]
            x2,y2 = self.path[-10]
#            x1 += 10; y1 += 10
#            x2 += 10; y2 += 10
        if y1 < y2:
#            LaserBlast(self, (x1,y1), (x2,y2))
            LaserBlast(self)
            self.shots += 1
            self.fire_rate = round(ENEMY_FIRE_RATE * game.speed())
    def new_path(self, points=None, path=None):
        if points is None:
#            print 'Enemy.new_path: points is None'
            self.points = []
        else:
#            print 'Enemy.new_path: points given'
            self.points = points
        if path is None:
#            print 'Enemy.new_path: path is None'
            self.path = plot_curve(self.points)
        else:
#            print 'Enemy.new_path: path given'
            self.path = path
        self.step = 0
    def tweak_score(self, score):
        """return tweaked score; override to tweak awarded points"""
        return score
    def _set_image(self, p1, p2):
        pos = self.rect.center
        degrees = calc_angle(p1, p2, to_screen=False)
        degrees = int(round(degrees/_SHIP_ANGLE_SIZE))
        degrees *= _SHIP_ANGLE_SIZE
#        if self.rank == (2,0):
#            print degrees
        self.image,self.hitmask = self.anim.get_angle(
            random.choice((0,1,1,2,2,2,2)), degrees)
        self.rect = self.image.get_rect()
        self.rect.center = pos
        self.angle = degrees

class EnemyFighter(Enemy):
    def __init__(self, rank, pos):
        Enemy.__init__(self, rank, pos)
## Thanks, Michael
##        self.anim = SpriteStripAnim(_SPACE1_SPRITE_SHEET, (11,259,36,23), 3,
##            colorkey=-1, hitmasks=False, angles=range(0,361,_SHIP_ANGLE_SIZE),
##            name='enemy_fighter')
##        self.anim.load_hitmasks(_FIGHTER_HITMASKS_FILENAME)
        self.anim = SpriteStripAnim(_SPACE1_SPRITE_SHEET, (11,259,36,23), 3,
            colorkey=-1, hitmasks=True, angles=range(0,361,_SHIP_ANGLE_SIZE),
            name='enemy_fighter')
    def new_path(self, points=None, path=None):
        game_left = game_rect.left
        game_right = game_rect.right
        if points is None:
            self_centerx = self.rect.centerx
            self_height = self.rect.height
            game_centerx = game_rect.centerx
            # Default path: enemy fighter flies an eccentric ? pattern
            points = [
                vec2d(self.rect.center),
                vec2d(443, 0),vec2d(527, 0),vec2d(527, 175),
                vec2d(527, 266),vec2d(428, 369),vec2d(286, 452),
                vec2d(164, 521),vec2d(86, 625),vec2d(86, 767+self_height),
            ]
            # Rules to tweak runtime path.
            #
            # Enemy points start out right-facing. If enemy is left-facing,
            # swap the x values to the other side. Then introduce some
            # randomness to the pattern as follows:
            #
            # tweak 2.x near to sprite.x (subject to left/right-facedness)
            # tweak 9.x to ram player
            # replace 1.x with sprite.x (subject to left/right-facedness)
            # set 3.x and 4.x to 2.x
            # vary 5.x through 9.x to kick out ? tail
            # vary 3.y through 7.y for height of ? tail
            if self_centerx >= game_centerx:
                # Default path already facing the right way; set 2.x, 9.x
                points[2].x = self_centerx + randrange(40, 80)
                if randrange(5) == 0:
                    points[9].x = game.player.rect.centerx
                else:
                    points[9].x = game_left
            else:
                # Flip default path to face other way; set 2.x, 9.x
                for p in points[2:]:
                    p.x = game_centerx - (p.x - game_centerx)
                points[2].x = self_centerx - randrange(40, 80)
                if randrange(5) == 0:
                    points[9].x = game.player.rect.centerx
                else:
                    points[9].x = game_right
            # Tweak the rest to add some unpredictability
            points[1].x = self_centerx
            points[3].x = points[4].x = points[2].x
            randx = randrange(0,60) - 30
            for i in (5,6,7,8,9):
                points[i].x += randx
            randy = randrange(0,120) - 100
            for i in (3,4,5,6,7):
                points[i].y += randy
        # Knock x inbounds
        for p in points[1:]:
            if p.x > game_right:
                p.x = game_right
            if p.x < game_left:
                p.x = game_left
        Enemy.new_path(self, points, path)
#        self.points = points
#        if path is None:
#            self.path = plot_curve(self.points)
#        self.step = 0
    def shoot(self):
        Enemy.shoot(self)
        if self.state == ENEMY_PATROL:
            mx = 0.2
        else:
            mx = 1
        fire_now = self.shots < ENEMY_MAX_SHOTS and \
            randrange(0,round(game.avg_fps*mx)) == 0
        if fire_now:
            angle_to_player = round(calc_angle(
                self.rect.center, game.player.rect.center, to_screen=False))
            spec_now = self.shots < ENEMY_MAX_SHOTS and \
                angle_to_player in range(self.angle-5, self.angle+5)
            if spec_now and self.spec_rate <= 0:
                # Special aimed attack
                LaserBlast(self, 'green')
                self.shots += 1
                self.spec_rate = round(ENEMY_FIRE_RATE * game.speed() / 2)
            elif self.fire_rate <= 0:
                # Normal attack
                self._strafing_attack()
    def tweak_score(self, score):
        """double default score if on patrol"""
        if self.state == ENEMY_PATROL:
            score *= 2
        return score

class EnemySweeper(Enemy):
    def __init__(self, rank, pos):
        Enemy.__init__(self, rank, pos)
## Thanks, Michael
##        self.anim = SpriteStripAnim(_SPACE2_SPRITE_SHEET, (191,105,43,35), 3,
##            colorkey=-1, hitmasks=False, angles=range(0,361,_SHIP_ANGLE_SIZE),
##            name='enemy_sweeper')
##        self.anim.load_hitmasks(_SWEEPER_HITMASKS_FILENAME)
        self.anim = SpriteStripAnim(_SPACE2_SPRITE_SHEET, (191,105,43,35), 3,
            colorkey=-1, hitmasks=True, angles=range(0,361,_SHIP_ANGLE_SIZE),
            name='enemy_sweeper')
    def new_path(self, points=None, path=None):
        game_left = game_rect.left
        game_right = game_rect.right
        if points is None:
            self_centerx = self.rect.centerx
            self_height = self.rect.height
            game_centerx = game_rect.centerx
            # Default path: enemy sweeper flies a wide S pattern
            points = [
                vec2d(self.rect.center),
                vec2d(400, 0),vec2d(527, 0),vec2d(527, 76),
                vec2d(527, 241),vec2d(383, 241),vec2d(193, 284),
                vec2d(17, 325),vec2d(17, 446),vec2d(129, 509),
                vec2d(275, 573),vec2d(574, 573),vec2d(574, 767+self_height),
            ]
            # Rules to tweak runtime path.
            #
            # Enemy points start out right-facing. If enemy is left-facing,
            # swap the x values to the other side. Then introduce some
            # randomness to the pattern as follows:
            #
            # tweak 2.x near to sprite.x (subject to left/right-facedness)
            # tweak 12.x to ram player
            # replace 1.x with sprite.x (subject to left/right-facedness)
            # set 3.x and 4.x to 2.x
            # vary 6.x through 9.x for width of S tail
            # vary 5.y through 7.y for top of S tail
            # vary 8.y through 11.y for bottom of S tail
            if self_centerx >= game_centerx:
                # Default path already facing the right way; set 2.x, 9.x
                points[2].x = self_centerx + randrange(20, 60)
                if randrange(5) == 0:
                    points[12].x = game.player.rect.centerx
                else:
                    points[12].x = game_left
            else:
                # Flip default path to face other way; set 2.x, 9.x
                for p in points[2:]:
                    p.x = game_centerx - (p.x - game_centerx)
                points[2].x = self_centerx - randrange(20, 60)
                if randrange(5) == 0:
                    points[12].x = game.player.rect.centerx
                else:
                    points[12].x = game_right
            # Tweak the rest to add some unpredictability
            points[1].x = self_centerx
            points[3].x = points[4].x = points[2].x
            randx = randrange(0,60) - 30
            for i in (6,7,8,9):
                points[i].x += randx
            randy = randrange(0,80) - 60
            for i in (5,6,7):
                points[i].y += randy
            randy = randrange(0,80) - 40
            for i in (8,9,10,11):
                points[i].y += randy
        # Knock x inbounds
        for p in points[1:]:
            if p.x > game_right:
                p.x = game_right
            if p.x < game_left:
                p.x = game_left
        Enemy.new_path(self, points, path)
#        self.points = points
#        if path is None:
#            self.path = plot_curve(self.points)
#        self.step = 0
    def shoot(self):
        Enemy.shoot(self)
        if self.state == ENEMY_PATROL:
            mx = 0.2
        else:
            mx = 1
        fire_now = self.shots < ENEMY_MAX_SHOTS and \
            randrange(0,round(game.avg_fps*mx)) == 0
        if fire_now:
            angle_to_player = round(calc_angle(
                self.rect.center, game.player.rect.center, to_screen=False))
            spec_now = self.shots < ENEMY_MAX_SHOTS and \
                angle_to_player in range(self.angle-5, self.angle+5)
            if spec_now and self.spec_rate <= 0:
                # Special aimed attack
                LaserBlast(self, 'red')
                self.shots += 1
                self.spec_rate = round(ENEMY_FIRE_RATE * game.speed() / 2)
            elif self.fire_rate <= 0:
                # Normal attack
                self._strafing_attack()

class EnemyKamikaze(Enemy):
    def new_path(self):
        pass

# the y position of the ranks in the standby formation
_STANDBY_Y = [100, 150, 200]
# the convoy paths that enemy sprites follow for each stage
# len(_CONVOY_PATHS) must equal len(_FLEETS)
_CONVOY_PATHS = [
    [
        vec2d(134, -20),
        vec2d(204, 163),vec2d(260, 274),vec2d(393, 323),
        vec2d(555, 358),vec2d(695, 375),vec2d(711, 443),
        vec2d(721, 500),vec2d(682, 550),vec2d(613, 565),
        vec2d(536, 559),vec2d(464, 539),vec2d(389, 508),
        vec2d(325, 467),vec2d(257, 415),vec2d(229, _STANDBY_Y[2]+100),
    ],
    [
        vec2d(526, -20),
        vec2d(526, 91),vec2d(502, 192),vec2d(445, 284),
        vec2d(392, 357),vec2d(315, 435),vec2d(215, 518),
        vec2d(116, 578),vec2d(43, 473),vec2d(130, 412),
        vec2d(212, 352),vec2d(310, 313),vec2d(434, 313),
        vec2d(540, 315),vec2d(626, 361),vec2d(688, 409),
        vec2d(767, 475),vec2d(682, 570),vec2d(604, 495),
        vec2d(539, 433),vec2d(479, 354),vec2d(420, _STANDBY_Y[2]+100),
    ],
    [
        vec2d(150, -20),
        vec2d(245, 24),vec2d(464, 24),vec2d(358, 94),
        vec2d(253, 176),vec2d(417, 219),vec2d(386, 283),
        vec2d(334, 392),vec2d(259, 467),vec2d(116, 567),
        vec2d(273, 487),vec2d(501, 420),vec2d(705, 409),
        vec2d(445, 341),vec2d(307, 341),vec2d(71, 412),
        vec2d(254, 419),vec2d(471, 490),vec2d(640, 569),
        vec2d(487, 477),vec2d(410, 393),vec2d(357, _STANDBY_Y[2]+100),
    ],
]
# _UNIT_TYPES decodes _FLEETS into classes
_UNIT_TYPES = {
    'f' : EnemyFighter,
    's' : EnemySweeper,
    'k' : EnemyKamikaze,
}
_MAX_ON_PATROL = {
    EnemyFighter : 3,
    EnemySweeper : 2,
    EnemyKamikaze: 1,
}
_N_ON_PATROL = {
    EnemyFighter : [],
    EnemySweeper : [],
    EnemyKamikaze: [],
}
_N_ON_STANDBY = {
    EnemyFighter : [],
    EnemySweeper : [],
    EnemyKamikaze: [],
}
# the standby formation and creation order (bottom-left to top-right) of
# enemies for each stage layout
_FLEETS = (
    [
        'ffssssff',
        'ffssssff',
        'ffffffff',
    ],
    [
        'ssffffss',
        'ssffffss',
        'ffffffff',
    ],
    [
        'fsfssfsf',
        'ffffffff',
        'ssssssss',
    ],
)
# the x,y position of all enemy sprites while in standby formation
_STANDBY_POS = [
    [],
    [],
    [],
]
# this produces a wavy back and forth motion for the standby formation
_STANDBY_STEPS = []
for i in range(0,3):
    _STANDBY_STEPS.append([])
    row = [0]*(8-i*4)  # old: (6-i*3)
    for j in range(0,3):
        row += [1]*8+[0]*14
    for j in range(0,4):
        row += [-1]*8+[0]*14
    row += [1]*8+[0]*12
    row += [0]*(i*4)  # old: (i*3)
    _STANDBY_STEPS[i].extend(row)
del row, i, j

class EnemyFactory(object):
    """manufacture enemies"""
    def __init__(self, level):
        global game, window, game_rect, jukebox
        game = game_globals.game
        window = game.window
        game_rect = window.rect
        jukebox = gumm_sound.jukebox
        jukebox.load_sound(join_path('sound','laser_26667e.ogg'),
            'enemy fires laser', volume=0.2, sound_type=SOUND_FX)
        jukebox.load_ascii_file(join_path('sound','enemy_explodes.dat'),
            'enemy explodes', volume=1, sound_type=SOUND_FX)
        self.stage = level % len(_FLEETS)
        self.fleet_size = 0
        self.fleet = []
        self._make_fleet()
        self.unit = 0
        self.done = False
        self.release_rate = 40 / game.speed()
        self.groups = game.groups
        self.n_patrol = 0
        self.n_standby = 0
        ## Every 5 levels add an extra baddy on patrol
        self.max_on_patrol = {}
        for k,v in _MAX_ON_PATROL.items():
            self.max_on_patrol[k] = v + int((level+1)/5)
        ## Every few seconds bump a random baddy's max-on-patrol
        self.more_on_patrol = game.max_fps * 2 * len(_MAX_ON_PATROL)/3
        self._draw_path_key = 0
        self.standby_step = 0
        self.standby_ticks = STANDBY_SPEED * game.speed()
    def update(self, standby_only=False):
        if self.unit == self.fleet_size:
            if not self.is_done():
                # Enemies filing in, wait for them to be on standby
                done = True
                for s in self.groups.enemies.sprites():
                    if s.state != ENEMY_STANDBY:
                        done = False
                        break
                self.done = done
        elif self.release_rate <= 0:
            # Make another enemy
            s = self.fleet[self.unit]
            s.update()
            self.groups.enemies.add(s)
            self.groups.all.add(s)
            self.unit += 1
            self.release_rate = 40 / game.speed()
        else:
            self.release_rate -= 1
        if not standby_only:
            # If player is dragging ass, put more in the air!
            if self.more_on_patrol <= 0:
                ## Every few seconds bump a random baddy's max-on-patrol
                k = random.choice(self.max_on_patrol.keys())
                self.max_on_patrol[k] += 0.1
                self.more_on_patrol = game.max_fps * 2 * len(_MAX_ON_PATROL)/3
            else:
                self.more_on_patrol -= 1
            self._send_patrol()
        self._step_standby()
    def _send_patrol(self):
        if not self.is_done():
            return
        patrol = _N_ON_PATROL
        standby = _N_ON_STANDBY
        for k in patrol.keys():
            del patrol[k][:]
            del standby[k][:]
        self.n_patrol = 0
        self.n_standby = 0
        for s in self.groups.enemies.sprites():
            if s.state == ENEMY_PATROL:
                patrol[type(s)].append(s)
                self.n_patrol += 1
            elif s.state == ENEMY_STANDBY:
                standby[type(s)].append(s)
                self.n_standby += 1
        for t in _MAX_ON_PATROL.keys():
#            send_now = randrange(0, round(game.avg_fps/2)) == 0
            send_now = randrange(0, round(game.avg_fps*2)) == 0
            if send_now and len(patrol[t]) < int(self.max_on_patrol[t]):
                send_n = int(self.max_on_patrol[t]) - len(patrol[t])
                if send_n > 1:
                    send_n = randrange(1,send_n)
#                if randrange(0,2):
#                    self._send_any(send_n, standby[t])
#                else:
#                    self._send_adjacent(send_n, standby[t])
                self._send_adjacent(send_n, standby[t])
    def _send_any(self, n, choices):
        return
        if n > 0 and len(choices) > 0:
            for i in range(n):
                which = random.choice(choices)
                which.state = ENEMY_PATROL
                which.new_path()
                choices.remove(which)
    def _send_adjacent(self, n, choices):
#        w = sys.stdout.write
#        f = sys.stdout.flush
#        w(repr(n))
        if n > 0 and len(choices) > 0:
#            w('#')
            which = random.choice(choices)
            which.state = ENEMY_PATROL
            which.new_path()
            choices.remove(which)
            x,y = which.rank
        n -= 1
        if n > 0 and len(choices) > 0:
#            w('+')
            which = None
            which_dist = 3
            for s in choices:
#                w('.')
                if which is None:
#                    w('1')
                    which = s
                    which_dist = which.rank[0]+which.rank[1]
                else:
#                    w('2')
                    rankx,ranky = s.rank
                    dx = max(x,rankx) - min(x,rankx)
                    dy = max(y,ranky) - min(y,ranky)
                    dist = dx + dy
                    if dist < which_dist:
#                        w('=')
                        which = s
                        which_dist = dist
            if which is not None:
#                w('^')
                which.state = ENEMY_PATROL
                which.new_path()
                choices.remove(which)
#        w('|')
#        f()
    def _step_standby(self):
        if not self.is_done():
            return
        if self.standby_step >= len(_STANDBY_STEPS[0]):
            self.standby_step = 0
        if self.standby_ticks <= 0:
            for i in range(0,len(_STANDBY_POS)):
                pos_row = _STANDBY_POS[i]
                step = _STANDBY_STEPS[i][self.standby_step]
                for p in pos_row:
                    p.x += step / game.speed()
            self.standby_ticks = STANDBY_SPEED * game.speed()
            self.standby_step += 1
        else:
            self.standby_ticks -= 1
    def is_done(self):
        return self.done
    def _make_fleet(self):
        window = game.window
        fleet_layout = _FLEETS[self.stage]
        screen_margin = 60
        screen_width = game_rect.width - (screen_margin*2)
        points = _CONVOY_PATHS[self.stage]
        path = plot_curve(points)
        for row_num in range(len(fleet_layout)-1, 0-1, -1):
            row = fleet_layout[row_num]
            row_len = len(row)
            _STANDBY_POS[row_num] = []
            y = _STANDBY_Y[row_num]
            for unit in range(0,row_len):
                UnitClass = _UNIT_TYPES[row[unit]]
                id = (row_num,unit)
                pos = _CONVOY_PATHS[self.stage][0]
                enemy = UnitClass(id, pos)
                enemy.state = ENEMY_CONVOY
                enemy.new_path(points, path)
                self.fleet.append(enemy)
                x = (unit * screen_width / row_len) + screen_margin
                _STANDBY_POS[row_num].append(vec2d(x,y))
        self.fleet_size = len(self.fleet)
    def _toggle_draw_path(self, key):
        if self._draw_path_key == key:
            self._draw_path_key = 0
        else:
            self._draw_path_key = key
    def draw_path(self):
        if self._draw_path_key == 0:
            return
        try:
            surf = window.surface
            group = game.groups.enemies
            draw_lines = pygame.draw.lines
            i = self._draw_path_key - K_0
            s = group.sprites()[i]
            if len(s.path) > 1:
                blue,grey,orange = Color('blue'),Color('grey'),Color('orange')
                draw_lines(surf, grey, False, s.points)
                for x,y in s.points:
                    pygame.draw.circle(surf, blue, (int(x),int(y)), 3)
                draw_lines(surf, orange, False, s.path)
        except IndexError:
            pass
